
import Foundation

typealias MessageCallback = (_ data:[String:Any]) async -> Void
typealias RequestCallback = (_ data:[String:Any]?, _ error:OspnError?) -> Void
typealias StatusCallback = (_ status:Bool) -> Void

class OspnNetService {
    var mHost = ""
    let mQueue: DispatchQueue = DispatchQueue(label: "OspnService", attributes: .concurrent)
    var mStatus = false
    var mRID: Int = 0
    let mLock = NSLock()
    var mIDMap = [Int:RequestCallback]()
    var mTimeLine: [(Int64, Int)] = []
    var mPostMessage: [[String:Any]] = []
    let mSemaphore = DispatchSemaphore(value: 0)
    var mCallback: MessageCallback? = nil
    var mStatusCallback: StatusCallback?
    var mWaiterCallback: StatusCallback?
    let mNet = OspnNetWork()
    
    func initService(_ hostIp:String){
        let defaults = UserDefaults.standard
        if !hostIp.isEmpty {
            defaults.set(hostIp, forKey: "ospn_hostIp")
            if !mHost.isEmpty && mHost != hostIp {
                mHost = hostIp
                mNet.disconnectToServer()
                return
            }
        }
        if mHost.isEmpty {
            mHost = defaults.string(forKey: "ospn_hostIp") ?? ""
        }
        if mHost.isEmpty {
            return
        }
        if mRID == 0 {
            mRID = Int(OsnUtils.getTimeStamp())
            mQueue.async(execute: taskReceive)
            mQueue.async(execute: taskTimer)
            taskPoster()
            Task { await taskHeart() }
        }
    }
    func taskReceive(){
        while(true){
            if mHost.isEmpty {
                sleep(1)
                continue
            }
            mStatus = false
            if !mNet.connectToServer(hostIp: mHost) {
                print("Connection failure")
                sleep(3)
                continue
            }
            mStatus = true
            print("Connection success")
            postStatus(true)
            while(true){
                let (data, error) = mNet.receiveFromServer()
                if error == OspnError.NetworkRecv {
                    print("\(String(describing: error))")
                    break
                }
                if let data = data {
                    recvPackage(data: data)
                }
            }
            print("Connection lose")
            mNet.disconnectToServer()
            postStatus(false)
        }
    }
    func taskHeart() async {
        var heart:[String:Any] = [:]
        heart["command"] = "Heart"
        while(true){
            sleep(15)
            if mStatus {
                //print("send heart")
                let (data, error) = await sendPackage(package: heart)
                if data == nil {
                    mStatus = false
                    print("heart timeout/error \(String(describing: error))")
                    mNet.disconnectToServer()
                }
            }
        }
    }
    func taskTimer(){
        while(true){
            usleep(100000)
            while(!mTimeLine.isEmpty){
                var callback: RequestCallback?
                mLock.lock()
                if let ts = mTimeLine.first {
                    if OsnUtils.getTimeStamp() >= ts.0 {
                        mTimeLine.removeFirst()
                        callback = mIDMap.removeValue(forKey: ts.1)
                    }
                }
                mLock.unlock()
                if let callback = callback {
                    callback(nil, OspnError.Timeout)
                }else{
                    break
                }
            }
        }
    }
    func taskPoster(){
        Task {
            while(true){
                let res = await withCheckedContinuation { continuation in
                    while mPostMessage.isEmpty {
                        mSemaphore.wait()
                    }
                    mLock.lock()
                    let data = mPostMessage.removeFirst()
                    mLock.unlock()
                    continuation.resume(returning: data)

                } as [String:Any]
                await mCallback?(res)
            }
        }
    }
    func postStatus(_ status: Bool){
        mStatusCallback?(status)
        mWaiterCallback?(status)
    }
    func recvPackage(data:Data) {
        guard let package = OsnUtils.fromData(data: data) else {
            let a = String(data: data, encoding: .utf8)
            print("recvPackage decode \(String(describing: a))")
            return
        }
        //print("\nrecvPackage \(package["command"] as? String ?? "nil") -> \(package)")
        print("\nrecvPackage \(package["command"] as? String ?? "nil") -> ")
        let error: OspnError? = if !gimService.isSuccess(package) {
            OspnError.Package(code: gimService.errCode(package))
        }else{
            nil
        }
        guard let rid = package["id"] as? String else {
            mPostMessage.append(package)
            mSemaphore.signal()
            return
        }
        mLock.lock()
        let callback = mIDMap.removeValue(forKey: Int(rid) ?? 0)
        mLock.unlock()
        if let callback = callback {
            callback(package, error)
        }else{
            mPostMessage.append(package)
            mSemaphore.signal()
        }
    }
    func makeRid(package:inout [String:Any]) -> Int {
        mLock.lock()
        mRID += 1
        let rid = mRID
        package["id"] = String(rid)
        mLock.unlock()
        return rid
    }
    func makeCallback(rid: Int, callback:@escaping RequestCallback, timeOut:Int){
        let timePoint = OsnUtils.getTimeStamp()+Int64(timeOut)
        
        mLock.lock()
        mIDMap[rid] = callback
        if let index = mTimeLine.firstIndex(where: { $0.0 > timePoint }) {
            mTimeLine.insert((timePoint, rid), at: index)
        } else {
            mTimeLine.append((timePoint, rid))
        }
        mLock.unlock()
    }
    func sendOnly(package:[String: Any]){
        //print("\nsendOnly: \(package["command"] as? String ?? "") -> \(package)")
        print("\nsendOnly: \(package["command"] as? String ?? "") -> ")
        _ = mNet.sendToServer(request: package)
    }
    func sendPackage(package:[String: Any] ,timeOut:Int = 5000) async -> ([String:Any]?,OspnError?) {
        var package = package
        let rid = makeRid(package: &package)

        //print("\nsendPackage: \(package["command"] ?? "") -> \(package)")
        print("\nsendPackage: \(package["command"] ?? "") -> ")
        if let error = mNet.sendToServer(request: package) {
            return (nil, error)
        }
        return await withUnsafeContinuation { (continuation: UnsafeContinuation<([String:Any]?, OspnError?), Never>) in
            let callback: RequestCallback = { data, error in
                //print("\(data ?? [:])")
                continuation.resume(returning: (data, error))
            }
            makeCallback(rid: rid, callback: callback, timeOut: timeOut)
        }
    }
    func waitService(timeOut:Int = 10000) -> Bool {
        if mStatus {
            return true
        }
        let sem:DispatchSemaphore = DispatchSemaphore(value: 0)
        mWaiterCallback = { status in
            if status {
                sem.signal()
            }
        }
        var waitTime = 0
        while(!mStatus && waitTime < timeOut){
            _ = sem.wait(timeout: DispatchTime.now() + .seconds(1))
            waitTime += 1000
        }
        mWaiterCallback = nil
        print("waitService \(mStatus)")
        return mStatus
    }
    func stopService(){
        mHost = ""
        mStatus = false
        mNet.disconnectToServer()
    }
}
let gnetService = OspnNetService()
